vue2 的响应式原理:在 vue 组件初始化时,先遍历 data 中的数据,对 data 中每个为对象类型的数据做 observe 处理,然后通过 Object.defineProperty 对 getter 和 setter 做拦截处理。接着在组件进行挂载时,会实例化一个渲染 Watcher,同时执行 render 函数生成组件 vnode,生成 vnode 过程如果有获取数据则会触发 getter 逻辑,进而该 data 数据就会收集到对应依赖。在该 data 数据更新后,会触发 setter 逻辑,通知所有依赖执行 patch 逻辑。

在响应式数据处理方面,相较于 vue2,vue3 的不同点有:

  • vue3 不会在组件初始化的时候就遍历 data 数据进行数据拦截处理,而是使用 Proxy 替代 Object.defineProperty,对整个对象数据进行代理,节省初始化时的开销。
  • vue2 使用的是发布订阅者配合 Object.defineProperty 实现响应式,而 vue3 则是用 Proxy 配合事件调度实现(effect + track + trigger)。

vue2 对数据做拦截处理在源码内部处理,而 vue3 则是将处理成响应式数据的接口暴露给用户。有:reactive、ref、readonly、shallowReactive、shadowReadonly。

以下面例子进行响应式源码分析:

<template>
	<span>{{ state.msg }}</span>
</template>

<script>
import { reactive } from 'vue'
export default {
  setup() {
    const state = reactive({
      msg: 'text'
    })
    return state
  }
}
</script>

# reactive 函数

源码分析路径:@vue/reactivity/dist/reactivity.esm-bundler.js

function reactive(target) {
    // 通过判断 __v_isReadonly 属性是否为 true,是的话不做处理
    if (isReadonly(target)) {
        return target;
    }
  	// 调用 createReactiveObject 函数,返回经过 proxy 代理的对象
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
  	// 非对象类型,不做处理
    if (!isObject(target)) {
        if ((process.env.NODE_ENV !== 'production')) {
            console.warn(`value cannot be made reactive: ${String(target)}`);
        }
        return target;
    }
  	// 当前对象本身就是一个被 proxy 代理过的对象,不做处理
    if (target["__v_raw" /* RAW */] &&
        !(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
        return target;
    }
  	// 通过缓存判断当前对象是否被 proxy 处理过,是的话返回缓存的代理对象
    const existingProxy = proxyMap.get(target);
    if (existingProxy) {
        return existingProxy;
    }
    // 无效对象不做处理
    const targetType = getTargetType(target);
    if (targetType === 0 /* INVALID */) {
        return target;
    }
  	// Proxy 处理,通过 targetType 设置 Proxy 的 handler
    const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
    proxyMap.set(target, proxy); // 缓存代理对象
    return proxy; // 返回代理对象
}

经分析,reactive 函数最终调用的是 createReactiveObject 函数。该函数的函数入参有:

  • target:被代理对象。
  • isReadonly:是否只读。
  • baseHandlers:target 为 Object 或者 Array 类型的 Proxy handler。
  • collectionHandlers:target 为 Set、Map、WeakSet、WeakMap 类型的 Proxy handler。
  • proxyMap:Proxy 代理过的对象数据缓存。

createReactiveObject 函数执行逻辑如下:

  • 如果 target 为非对象类型,不做处理,返回原 target 数据。
  • 判断当前对象本身是否是被 proxy 代理过的对象,是的话不做处理,返回原 target 数据。
  • 通过 proxyMap 缓存判断当前对象是否被 proxy 处理过,是的话返回缓存的代理对象。
  • 判断是否为无效对象,是的话不做处理,返回原 target 数据。
  • 通过不同的 targetType 获取对应的 Proxy handler,进而进行 Proxy 处理。在例子中,由于 target 为 Object 类型,所以对应的 Proxy handler 为 baseHandlers。
  • 最后缓存并返回代理对象。

# baseHandlers

经分析,baseHandlers 其实就是 mutableHandlers。

const mutableHandlers = {
    get: createGetter(),
    set: createSetter(),
    deleteProperty,
    has,
    ownKeys
};

# createGetter 函数

createGetter 函数源码:

function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
      	// 获取特殊 key 值处理
        if (key === "__v_isReactive" /* IS_REACTIVE */) {
            return !isReadonly;
        }
        else if (key === "__v_isReadonly" /* IS_READONLY */) {
            return isReadonly;
        }
        else if (key === "__v_isShallow" /* IS_SHALLOW */) {
            return shallow;
        }
        else if (key === "__v_raw" /* RAW */ &&
            receiver ===
                (isReadonly
                    ? shallow
                        ? shallowReadonlyMap
                        : readonlyMap
                    : shallow
                        ? shallowReactiveMap
                        : reactiveMap).get(target)) {
            return target;
        }
      	// 数组类型处理且获取 key 值为数组原型上的一些方法,对该方法做处理
        const targetIsArray = isArray(target);
        if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
            return Reflect.get(arrayInstrumentations, key, receiver);
        }
        // 获取对应值,对值做不同类型处理
        const res = Reflect.get(target, key, receiver);
      	// key 为 Symbol 类型,不做处理,返回 key 对应的值
        if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
            return res;
        }
      	// 不是只读对象,则调用 track 函数对 target 中的 key 值做依赖处理
        if (!isReadonly) {
            track(target, "get" /* GET */, key);
        }
      	// shallowReactive 处理,不对子对象做响应式处理
        if (shallow) {
            return res;
        }
        if (isRef(res)) {
            // ref unwrapping - does not apply for Array + integer key.
            const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
            return shouldUnwrap ? res.value : res;
        }
        if (isObject(res)) {
          	// 非只读对象,对子对象也做响应也做响应式处理
            return isReadonly ? readonly(res) : reactive(res);
        }
        return res;
    };
}

函数执行逻辑:

  • 先对特殊的 key 做特殊处理。如果获取的 key 是特殊的 key,则做对应特殊处理。
  • 接着判断 target 数据是否为数组类型,是的话且获取的 key 为数组原型上的一些方法名,则对该方法做处理。
  • 然后判断 key 是否为 Symbol 类型,是的话不做处理,直接返回 key 对应的值。
  • 如果非只读对象,则调用 track 函数对 target 中的 key 值做依赖处理。
  • 接着判断是否为 shallowReactive 浅处理
    • 是的话则不对子对象做响应式递归处理。
    • 不是的话且 key 对应的值为 Object 类型,则递归调用 reactive 对子对象做响应式处理。
  • 返回结果。

# track 函数

track 函数的作用是:有激活的 effect 时,对该 effect 进行收集。

function track(target, type, key) {
  	// 全局变量 shouldTrack 和 activeEffect 都存在时,执行依赖收集
    if (shouldTrack && activeEffect) {
      	// 判断当前的 target 数据对象是否存在于缓存中,没有缓存过,则缓存当前的 target 数据到 targetMap 中
        let depsMap = targetMap.get(target);
        if (!depsMap) {
            targetMap.set(target, (depsMap = new Map()));
        }
      	// 获取当前 key 的依赖集合,如果没有依赖,则创建一个
        let dep = depsMap.get(key);
        if (!dep) {
            depsMap.set(key, (dep = createDep()));
        }
        const eventInfo = (process.env.NODE_ENV !== 'production')
            ? { effect: activeEffect, target, type, key }
            : undefined;
        trackEffects(dep, eventInfo); // 调用 trackEffects 函数
    }
}

function trackEffects(dep, debuggerEventExtraInfo) {
    let shouldTrack = false;
  	// effect track 的深度判断,最大为 30
    if (effectTrackDepth <= maxMarkerBits) {
        if (!newTracked(dep)) {
            dep.n |= trackOpBit; // set newly tracked
            shouldTrack = !wasTracked(dep);
        }
    }
    else {
        // Full cleanup mode.
        shouldTrack = !dep.has(activeEffect);
    }
    if (shouldTrack) {
        dep.add(activeEffect); // 将当前 target 对象的 key 的依赖者 activeEffect 添加到 dep 依赖者集合中
        activeEffect.deps.push(dep); // 将当前的 dep 保存到 activeEffect.deps 中
        if ((process.env.NODE_ENV !== 'production') && activeEffect.onTrack) {
            activeEffect.onTrack(Object.assign({ effect: activeEffect }, debuggerEventExtraInfo));
        }
    }
}

上面源码执行逻辑为:

  • 如果当前没有激活的 effect,则不进行依赖收集。
  • 有激活的 effect 情况,会通过 target 对象在 targetMap 缓存中获取对应的依赖集合 depsMap,如果没有则创建一个 depsMap 并缓存在 targetMap 中。
  • 接着会通过 depsMap 获取当前 target 对象的属性 key 的依赖集合,没有则创建一个。
  • 然后对 effect track 的深度判断,最大为 30。
  • 最后将当前激活的 effect 添加到属性 key 的依赖集合 dep 中,并将 dep 保存到当前激活的 effect 的 deps 中。

# createSetter 函数

function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        let oldValue = target[key];
      	// 只读类型数据不做处理
        if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
            return false;
        }
        if (!shallow && !isReadonly(value)) {
            if (!isShallow(value)) {
                value = toRaw(value);
                oldValue = toRaw(oldValue);
            }
            if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
                oldValue.value = value;
                return true;
            }
        }
        const hadKey = isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key);
        const result = Reflect.set(target, key, value, receiver);
        // don't trigger if target is something up in the prototype chain of original
        if (target === toRaw(receiver)) {
            if (!hadKey) {
                trigger(target, "add" /* ADD */, key, value);
            }
            else if (hasChanged(value, oldValue)) {
                trigger(target, "set" /* SET */, key, value, oldValue);
            }
        }
        return result;
    };
}

经分析,可以看到函数最后主要执行 trigger 函数。

# trigger 函数

function trigger(target, type, key, newValue, oldValue, oldTarget) {
    const depsMap = targetMap.get(target);
  	// 当前 target 对象没有被追踪处理,直接返回
    if (!depsMap) {
        return;
    }
    let deps = [];
    if (type === "clear" /* CLEAR */) {
      	// 当前的所有依赖正在被清理,将触发所有的 effect
        deps = [...depsMap.values()];
    }
    else if (key === 'length' && isArray(target)) {
      	// 数组类型且设置 key 值为 length(修改数组长度),遍历数组搜集搜有的依赖
        depsMap.forEach((dep, key) => {
            if (key === 'length' || key >= newValue) {
                deps.push(dep);
            }
        });
    }
    else {
      	// SET ADD DELETE 类型
        if (key !== void 0) {
          	// 获取当前 key 的 dep(即:key 的所有 effect Set 集合)
            deps.push(depsMap.get(key));
        }
      	// 迭代类型的 key 处理
        switch (type) {
            case "add" /* ADD */:
                if (!isArray(target)) {
                    deps.push(depsMap.get(ITERATE_KEY));
                    if (isMap(target)) {
                        deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    deps.push(depsMap.get('length'));
                }
                break;
            case "delete" /* DELETE */:
                if (!isArray(target)) {
                    deps.push(depsMap.get(ITERATE_KEY));
                    if (isMap(target)) {
                        deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                break;
            case "set" /* SET */:
                if (isMap(target)) {
                    deps.push(depsMap.get(ITERATE_KEY));
                }
                break;
        }
    }
    const eventInfo = (process.env.NODE_ENV !== 'production')
        ? { target, type, key, newValue, oldValue, oldTarget }
        : undefined;
    if (deps.length === 1) {
        if (deps[0]) {
            if ((process.env.NODE_ENV !== 'production')) {
                triggerEffects(deps[0], eventInfo);
            }
            else {
                triggerEffects(deps[0]);
            }
        }
    }
    else {
        const effects = [];
        for (const dep of deps) {
            if (dep) {
                effects.push(...dep);
            }
        }
        if ((process.env.NODE_ENV !== 'production')) {
            triggerEffects(createDep(effects), eventInfo);
        }
        else {
            triggerEffects(createDep(effects));
        }
    }
}

上面函数的执行逻辑:先对 target 对象的 depsMap 做判空处理;接着根据传入的 type 类型获取当前的 target 对象的属性 key 在 depsMap 中的 effect Set 集合列表,并将该集合列表存放到变量 deps 中,最后调用 triggerEffects 函数。

# triggerEffects 函数

function triggerEffects(dep, debuggerEventExtraInfo) {
    for (const effect of isArray(dep) ? dep : [...dep]) {
        if (effect !== activeEffect || effect.allowRecurse) {
            if ((process.env.NODE_ENV !== 'production') && effect.onTrigger) {
                effect.onTrigger(extend({ effect }, debuggerEventExtraInfo));
            }
            if (effect.scheduler) {
                effect.scheduler();
            }
            else {
                effect.run();
            }
        }
    }
}

函数执行逻辑:由于 dep 为 Set 数据结构,如果 dep 非数组类型,会先将 dep 转为数组。遍历 dep 列表,触发执行 effect。执行 effect 有三种方式:

  • onTrigger:主要是开发环境 debug 使用。
  • scheduler:通过调度程序执行 effect.run。
  • run:直接调用 effect.run。

scheduler 调度程序的定义在createAppAPI -> mount -> render -> patch -> processComponent -> mountComponent -> setupRenderEffect中,在 setupRenderEffect 函数中,会实例化一个 ReactiveEffect 对象。scheduler 的定义源码如下:

const effect = (instance.effect = new ReactiveEffect(
  componentUpdateFn,
  () => queueJob(instance.update), // scheduler
  instance.scope));
const update = (instance.update = effect.run.bind(effect));

分析可得,queueJob 的函数参数 instance.update 其实就是 effect.run 函数。queueJob 函数源码比较复杂,后面再单独拎出来分析。其实,queueJob 最后执行的也是 effect.run 函数。effect.run 函数会执行实例化 ReactiveEffect 对象时传入的回调 componentUpdateFn 函数,该函数会执行 patch 逻辑更新视图数据。

接下来看 effect 实现逻辑。

# effect 实现逻辑

在 vue 组件执行 mount 时,会先通过 setupComponent 函数实现组件实例初始化,对 data 数据进行响应式处理,即:通过 reactive 等函数处理的数据;然后就通过 setupRenderEffect 函数创建一个渲染 effect。

mount 函数主要源码如下:

function createAppAPI(render, hydrate) {
  return function createApp(rootComponent, rootProps = null) {
    if (!isFunction(rootComponent)) {
      rootComponent = Object.assign({}, rootComponent);
    }
    const app = (context.app = {
      _uid: uid++,
      _component: rootComponent,
      _props: rootProps,
      _container: null,
      _context: context,
      _instance: null,
      version,
      mount(rootContainer, isHydrate, isSVG) {
        if (!isMounted) {
          const vnode = createVNode(rootComponent, rootProps);
          // ...
          // 执行 render 函数
          render(vnode, rootContainer, isSVG);
          // ...
          return getExposeProxy(vnode.component) || vnode.component.proxy;
        }
        // ...
      }
    })
    return app;
  };
}

render 函数主要源码如下:

function baseCreateRenderer(options, createHydrationFns) {
  const render = (vnode, container, isSVG) => {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true);
      }
    }
    else {
      // 执行 patch 函数
      patch(container._vnode || null, vnode, container, null, null, null, isSVG);
    }
    flushPostFlushCbs();
    container._vnode = vnode;
  };

  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  };
}

经分析,render 函数执行的是 patch 函数。

由于是 vue 组件的挂载,所以 patch 函数内部主要源码为:

const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = (process.env.NODE_ENV !== 'production') && isHmrUpdating ? false : !!n2.dynamicChildren) => {
  // ...
  const { type, ref, shapeFlag } = n2;
  switch (type) {
    // ...
    default:
      if (shapeFlag & 1 /* ELEMENT */) {
        processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
      }
      else if (shapeFlag & 6 /* COMPONENT */) {
        // 执行组件类型的 patch 
        processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
      }
      // ...
  }
	// ...
};

processComponent 函数主要源码:

const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
  n2.slotScopeIds = slotScopeIds;
  if (n1 == null) {
    if (n2.shapeFlag & 512 /* COMPONENT_KEPT_ALIVE */) {
      parentComponent.ctx.activate(n2, container, anchor, isSVG, optimized);
    }
    else {
      mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
    }
  }
  else {
    updateComponent(n1, n2, optimized);
  }
};

上面源码比较简单,挂载执行的是 mountComponent 函数。

mountComponent 函数主要执行:

const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
  // ... 

  setupComponent(instance); // 实现组件实例初始化,对 data 数据进行响应式处理,即:通过 reactive 等函数处理的数据

  // ...

  setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized); // 创建一个渲染 effect

	 // ...
}

# setupRenderEffect 函数

函数主要源码如下:

const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
  const componentUpdateFn = () => {
    // 触发 effect.run 执行的回调函数
    if (!instance.isMounted) {
       // ...
       
       // 生成 vnode
      	const subTree = (instance.subTree = renderComponentRoot(instance));
       // 主要执行 patch 逻辑
       patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
       // ...
    }
    // ...
  }
	// 创建一个 effect
  const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () => queueJob(instance.update), instance.scope));
  
  // instance.update 实际上就是 effect.run 函数
  const update = (instance.update = effect.run.bind(effect));
  update.id = instance.uid;

  // ...

  update();
}

setupRenderEffect 函数先创建一个 effect,然后将 effect.run 函数赋值给 instant.update,接着手动执行一次 update 函数;update 函数内部会先生成 vnode,生成 vnode 过程会如果有获取 data 数据则会触发 Proxy getter 逻辑,实现对应数据的依赖搜集,生成 vnode 之后会执行 patch 更新视图。

# ReactiveEffect

源码如下:

class ReactiveEffect {
    constructor(fn, scheduler = null, scope) {
        this.fn = fn; // 回调函数,也就是上面的 componentUpdateFn 函数
        this.scheduler = scheduler;
        this.active = true;
        this.deps = [];
        this.parent = undefined;
        recordEffectScope(this, scope);
    }
    run() {
        if (!this.active) {
            return this.fn();
        }
        let parent = activeEffect;
        let lastShouldTrack = shouldTrack;
        while (parent) {
            if (parent === this) {
                return;
            }
            parent = parent.parent;
        }
        try {
            this.parent = activeEffect;
            activeEffect = this;
            shouldTrack = true;
            trackOpBit = 1 << ++effectTrackDepth;
            if (effectTrackDepth <= maxMarkerBits) {
                initDepMarkers(this);
            }
            else {
                cleanupEffect(this);
            }
            return this.fn(); // 执行回调函数
        }
        finally {
            if (effectTrackDepth <= maxMarkerBits) {
                finalizeDepMarkers(this);
            }
            trackOpBit = 1 << --effectTrackDepth;
            activeEffect = this.parent;
            shouldTrack = lastShouldTrack;
            this.parent = undefined;
            if (this.deferStop) {
                this.stop();
            }
        }
    }
    stop() {
        if (activeEffect === this) {
            this.deferStop = true;
        }
        else if (this.active) {
            cleanupEffect(this);
            if (this.onStop) {
                this.onStop();
            }
            this.active = false;
        }
    }
}

可见,effect.run 函数最后执行的是回调函数 componentUpdateFn。

# 总结

vue3 的响应式逻辑是在组件执行 mount 时,会先通过 setupComponent 函数初始化组件实例,实现 data 数据的 Proxy 代理,也就是实现 Proxy 的 setter、getter 等逻辑;接着通过 setupRenderEffect 创建一个渲染 effect,然后手动执行一次 effect.run 函数,effect.run 函数内部也就执行了传入的回调函数,该回调函数会先生成 vnode,生成 vnode 过程如果有获取 data 数据则会触发对应的 getter 逻辑,实现依赖收集(也就是收集 effect),生成完 vnode 之后,会执行 patch 逻辑更新视图。